﻿using System;
using System.Collections;
using System.Collections.Generic;
using linq = Microsoft.Linq.Expressions;
using System.Linq;
using Bling.Util;

namespace Bling.DSL {
  public partial interface Assign {
    bool Accept<T>(Expr<T> LHS, Expr<T> RHS);
    Assign Copy();
  }
  public partial interface ExprVisitor<R> {
    R Visit<T>(Expr<T> value);
  }
  public partial interface BaseExpr {
    Type TypeOfT { get; }
    object BaseEval<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL>;
    R Visit<R>(ExprVisitor<R> v);
    bool Solve(Assign assign, BaseExpr value);
  }
  public partial interface BaseExpr<T> : BaseExpr { }
  public abstract partial class Expr : BaseExpr {
    public abstract Type TypeOfT { get; }
    public abstract object BaseEval<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL>;
    public abstract R Visit<R>(ExprVisitor<R> v);
    public abstract bool Solve(Assign assign, BaseExpr value);


  }
  public partial interface BindableExpr<T> : BaseExpr<T> {
    bool BindWith(Expr<T> value);
  }
  public partial interface AssignNowExpr<T> : BaseExpr<T> {
    Action SetNow(Expr<T> value);
    Action<S> SetNow<S>(Expr<T> value, ParameterExpr<S> param);
    Action<S, U> SetNow<S, U>(Expr<T> value, ParameterExpr<S> paramA, ParameterExpr<U> paramB);
    Action<S, U, V> SetNow<S, U, V>(Expr<T> value, ParameterExpr<S> paramA, ParameterExpr<U> paramB, ParameterExpr<V> paramC);
    Action<S, U, V, W> SetNow<S, U, V, W>(Expr<T> value, ParameterExpr<S> paramA, ParameterExpr<U> paramB, ParameterExpr<V> paramC, ParameterExpr<W> paramD);
    // really set now, don't bother creating a function.
    bool SetNow(T value);
  }
  public partial class BindAssign : Assign {
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is BindableExpr<T>)
        return ((BindableExpr<T>)LHS).BindWith(RHS);
      return false;
    }
    public Assign Copy() { return this; }
  }
  public partial class ApplyNowAssign : Assign {
    public Assign Copy() { return this; }
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is AssignNowExpr<T>) {
        ((AssignNowExpr<T>)LHS).SetNow(RHS)();
        return true;
      }
      return false;
    }
  }

  public partial class NowAssign : Assign {
    public readonly List<Action> Results = new List<Action>();
    public Assign Copy() { return this; }
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is AssignNowExpr<T>) {
        var now = ((AssignNowExpr<T>)LHS).SetNow(RHS);
        if (now == null) return false;
        Results.Add(now);
        return true;
      }
      return false;
    }
  }
  public partial interface ParameterExpr : BaseExpr {
    string Name { get; set; }
  }
  public partial class ParameterExpr<T> : Expr<T>, ParameterExpr {
    public string Name { get; set; }
    public ParameterExpr() {
      Name = "P" + (GetHashCode() % 100);
    }
    public override string ToString() { return Name; }
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is ParameterEval<EVAL>)
        return ((ParameterEval<EVAL>)txt).Parameter(this);
      throw new NotImplementedException();
    }
  }
  public partial interface ParameterEval<EVAL> where EVAL : Eval<EVAL> {
    Eval<EVAL>.Info<T> Parameter<T>(ParameterExpr<T> value);
  }

  public partial class NowAssign<S> : Assign {
    public readonly ParameterExpr<S> Argument;
    public NowAssign() {
      Argument = new ParameterExpr<S>() { Name = "Arg_" + (this.GetHashCode() % 100) };
    }
    public readonly List<Action<S>> Results = new List<Action<S>>();
    public Assign Copy() { return this; }
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is AssignNowExpr<T>) {
        var now = ((AssignNowExpr<T>)LHS).SetNow<S>(RHS, Argument);
        if (now == null) return false;
        Results.Add(now);
        return true;
      }
      return false;
    }
  }
  public partial class NowAssign<S,U> : Assign {
    public readonly ParameterExpr<S> ArgumentA;
    public readonly ParameterExpr<U> ArgumentB;
    public NowAssign() {
      ArgumentA = new ParameterExpr<S>() { Name = "ArgA_" + (this.GetHashCode() % 100) };
      ArgumentB = new ParameterExpr<U>() { Name = "ArgB_" + (this.GetHashCode() % 100) };
    }
    public NowAssign(ParameterExpr<S> ArgumentA) {
      this.ArgumentA = ArgumentA;
      ArgumentB = new ParameterExpr<U>() { Name = "ArgB_" + (this.GetHashCode() % 100) };
    }
    public readonly List<Action<S, U>> Results = new List<Action<S, U>>();
    public Assign Copy() { return this; }
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is AssignNowExpr<T>) {
        var now = ((AssignNowExpr<T>)LHS).SetNow<S,U>(RHS, ArgumentA, ArgumentB);
        if (now == null) return false;
        Results.Add(now);
        return true;
      }
      return false;
    }
  }
  public partial class NowAssign<S, U, V> : Assign {
    public readonly ParameterExpr<S> ArgumentA;
    public readonly ParameterExpr<U> ArgumentB;
    public readonly ParameterExpr<V> ArgumentC;
    public NowAssign() {
      ArgumentA = new ParameterExpr<S>() { Name = "ArgA_" + (this.GetHashCode() % 100) };
      ArgumentB = new ParameterExpr<U>() { Name = "ArgB_" + (this.GetHashCode() % 100) };
      ArgumentC = new ParameterExpr<V>() { Name = "ArgC_" + (this.GetHashCode() % 100) };
    }
    public readonly List<Action<S, U, V>> Results = new List<Action<S, U, V>>();
    public Assign Copy() { return this; }
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is AssignNowExpr<T>) {
        var now = ((AssignNowExpr<T>)LHS).SetNow<S, U, V>(RHS, ArgumentA, ArgumentB, ArgumentC);
        if (now == null) return false;
        Results.Add(now);
        return true;
      }
      return false;
    }
  }
  public partial class NowAssign<S, U, V, W> : Assign {
    public readonly ParameterExpr<S> ArgumentA;
    public readonly ParameterExpr<U> ArgumentB;
    public readonly ParameterExpr<V> ArgumentC;
    public readonly ParameterExpr<W> ArgumentD;
    public NowAssign() {
      ArgumentA = new ParameterExpr<S>() { Name = "ArgA_" + (this.GetHashCode() % 100) };
      ArgumentB = new ParameterExpr<U>() { Name = "ArgB_" + (this.GetHashCode() % 100) };
      ArgumentC = new ParameterExpr<V>() { Name = "ArgC_" + (this.GetHashCode() % 100) };
      ArgumentD = new ParameterExpr<W>() { Name = "ArgD_" + (this.GetHashCode() % 100) };
    }
    public readonly List<Action<S, U, V, W>> Results = new List<Action<S, U, V, W>>();
    public Assign Copy() { return this; }
    public bool Accept<T>(Expr<T> LHS, Expr<T> RHS) {
      if (LHS is AssignNowExpr<T>) {
        var now = ((AssignNowExpr<T>)LHS).SetNow<S, U, V, W>(RHS, ArgumentA, ArgumentB, ArgumentC, ArgumentD);
        if (now == null) return false;
        Results.Add(now);
        return true;
      }
      return false;
    }
  }
  public abstract partial class TranslateEval<EVAL> : Eval<EVAL> where EVAL : Eval<EVAL> {
    public class MyInfo<T> : Info<T> { 
      public Expr<T> Value;
    }
    public override Eval<EVAL>.Info<S> UpCast<T, S>(Eval<EVAL>.Info<T> info) {
      return new MyInfo<S>() {
        Value = new UpCastExpr<T, S>(((MyInfo<T>) info).Value)
      };
    }
    public virtual Expr<T> Translate<T>(Expr<T> value) { return ((MyInfo<T>) value.Eval<EVAL>(this)).Value; }
    public Info<T> NoTranslation<T>(Expr<T> value) {
      return new MyInfo<T>() { Value = value };
    }
    public override Info<T> Constant<T>(Constant<T> constant) { return NoTranslation(constant); }
  }
  public class ReplaceEval : TranslateEval<ReplaceEval> {
    public readonly Dictionary<BaseExpr, BaseExpr> Replace = new Dictionary<BaseExpr, BaseExpr>();
    public override Expr<T> Translate<T>(Expr<T> value) {
      if (Replace.ContainsKey(value)) return (Expr<T>)Replace[value];
      else return base.Translate<T>(value);
    }
  }

  public interface ForceEval {
    object Force<T>(Func<T> F);
    object Force<T, S>(Expr<T> index, Func<T, S> F);
  }

  public class ApplyArg<X,T> : Expr<T> {
    public Func<X, T> F;
    public string Name = "arg";
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is ApplyEval<X>) return
        (Eval<EVAL>.Info<T>)(object)
        new ApplyEval<X>.MyInfo<T>() {
          Value = x => {
            return F(x);
          }
        };
      throw new NotSupportedException();
    }
    public override string ToString() {
      return Name;
    }
  }
  public partial class ApplyEval<X> : Eval<ApplyEval<X>>, ForceEval, TableEval<ApplyEval<X>> {
    public class MyInfo<T> : Info<T> { 
      public Func<X, T> Value;
    }
    public override Eval<ApplyEval<X>>.Info<S> UpCast<T, S>(Eval<ApplyEval<X>>.Info<T> info) {
      return new MyInfo<S>() { Value = (x) => ((MyInfo<T>)info).Value(x) };
    }
    public object Force<T>(Func<T> F) {
      return new MyInfo<T>() { Value = x => F() };
    }
    public object Force<T, S>(Expr<T> index, Func<T, S> F) {
      var G = ((MyInfo<T>)index.Eval(this)).Value;
      return new MyInfo<S>() { Value = x => F(G(x)) };
    }
    public override Info<T> Constant<T>(Constant<T> constant) {
      return new MyInfo<T>() { Value = x => constant.Value };
    }
    public Info<T> Table<T>(TableExpr<T> value) {
      var idx = ((MyInfo<int>) value.Index.Eval(this)).Value;
      return new MyInfo<T>() {
        Value = (x) => {
          return ((MyInfo<T>) value.Values[idx(x)].Eval(this)).Value(x);
        },
      };
    }
    public Info<T> Block<T>(T[] Values, Info<int> idx) {
      var idx0 = ((MyInfo<int>)idx).Value;
      return new MyInfo<T>() {
        Value = (x) => Values[idx0(x)],
      };
    }

  }


  public abstract partial class Expr<T> : Expr, BaseExpr<T> {

    public override R Visit<R>(ExprVisitor<R> v) {
      return v.Visit<T>(this);
    }
    public virtual Expr Identity { get { return this; } }
    private Func<T> CurrentValue0;
    public T CurrentValue {
      get {
        if (CurrentValue0 == null) CurrentValue0 = Linq.DefaultLinqEval.Eval<T>(this);
        return CurrentValue0();
      }
    }
    public virtual bool HasCurrentValue { get { return true; } }
    public static implicit operator Expr<T>(T t) { return new Constant<T>(t); }

    public override Type TypeOfT {
      get { return typeof(T); }
    }
    public virtual bool Solve(Assign P, Expr<T> other) {
      var ret = P.Accept(this, other);
      return ret;
    }
    public override bool Solve(Assign assign, BaseExpr value) {
      return Solve(assign, (Expr<T>)value);
    }

    public abstract Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL>;
    public override object BaseEval<EVAL>(Eval<EVAL> txt) {
      return Eval(txt);
    }
    private class StupidExpr : Expr<T> {
      public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
        return (new Constant<T>()).Eval(txt);
      }
    }
    /// <summary>
    /// Creates a value around the specified function. The function will be called each time the value is evaluated.
    /// </summary>
    public static Expr<T> FromFunc(Func<T> F) {
      return (new StupidExpr()).Map<T>((s) => F());
    }
    public Expr<S> Map<S>(Func<T, S> F) { return Map(F, null); }
    public Expr<T> Bind {
      set {
        if (!Solve(new BindAssign(), value))
          throw new NotSupportedException();
      }
    }
    public Expr<S> DownCast<S>() where S : T {
      return new DownCastOperator<S>().Make(this);
    }
    public Expr<S> ExplicitConvert<S>() {
      return new ExplicitConvertOperator<S>().Make(this);
    }
  }
  public class CursorNextPrev<T> {
    private readonly Func<IntBl, T> F;
    public CursorNextPrev(Func<IntBl, T> F) { this.F = F; }
    public T this[IntBl Offset] { get { return F(Offset); } }
  }
  public interface Cursor<T> {
    T At { get; }
    CursorNextPrev<T> Next { get; }
    CursorNextPrev<T> Prev { get; }
    IntBl Index { get; }
    T this[IntBl Offset] { get; }
    IntBl Count { get; }
  }

  public partial interface BaseEval<EVAL> where EVAL : Eval<EVAL> {

  }
  public abstract partial class Eval<EVAL> : BaseEval<EVAL> where EVAL : Eval<EVAL> {
    public interface Info { }
    public interface Info<T> : Info { }
    public abstract Info<K> UpCast<G, K>(Info<G> info) where G : K;
    public abstract Info<T> Constant<T>(Constant<T> constant);
  }
  public partial class UpCastExpr<T, S> : Expr<S> where T : S {
    protected readonly Expr<T> Underlying;
    public UpCastExpr(Expr<T> Underlying) {
      this.Underlying = Underlying;
    }
    public override Expr Identity {
      get { return Underlying; }
    }

    public override Eval<EVAL>.Info<S> Eval<EVAL>(Eval<EVAL> txt) {
      return txt.UpCast<T,S>(Underlying.Eval(txt));
    }
    // covariance hazzards.
    public override bool Solve(Assign assign, BaseExpr value) {
      return false;
    }
    public override bool Solve(Assign P, Expr<S> other) {
      return false;
    }
    public override string ToString() {
      return Underlying.ToString() + " : " + (typeof(S)).Name;
    }
    public override int GetHashCode() {
      return Underlying.GetHashCode() + typeof(S).GetHashCode();
    }
    public override bool Equals(object obj) {
      if (obj is UpCastExpr<T, S>) return ((UpCastExpr<T, S>)obj).Underlying.Equals(Underlying);
      return base.Equals(obj);
    }



  }
  public interface BaseConstant : BaseExpr {
    object BaseValue { get; }
  }

  public class Constant<T> : Expr<T>, BaseConstant {
    public readonly T Value;
    public Constant(T value) { this.Value = value; }
    public Constant() : this(default(T)) { }
    public override string ToString() {
      return Value.ToString();
    }
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      return txt.Constant(this);
    }
    public override int GetHashCode() {
      return Value == null ? 0 : Value.GetHashCode();
    }
    public override bool Equals(object obj) {
      if (Value == null) return obj == null;
      else if (obj is Constant<T>) return Value.Equals(((Constant<T>)obj).Value);
      else return base.Equals(obj);
    }
    public object BaseValue { get { return Value; } }
  }
  public interface RefreshManager {
    void Register(Action f, Expr<bool> when);
  }
  public interface TableEval<EVAL> where EVAL : Eval<EVAL> {
    Eval<EVAL>.Info<T> Table<T>(TableExpr<T> value);
    Eval<EVAL>.Info<T> Block<T>(T[] Values, Eval<EVAL>.Info<int> idx);
  }

  public partial class TableExpr<T> : Expr<T> {
    public readonly Expr<int> Index;
    public readonly Expr<T>[] Values;
    public TableExpr(Expr<T>[] Values, Expr<int> Index) {
      this.Values = Values; this.Index = Index;
    }
    public override bool Solve(Assign P, Expr<T> other) {
      if (base.Solve(P, other)) return true;
      for (int i = 0; i < Values.Length; i++) {
        if (!Values[i].Solve(P, other)) return false;

      }
      return false;
    }
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is TableEval<EVAL>) 
        return ((TableEval<EVAL>)txt).Table<T>(this);
      // do a stupid implementation. 
      Expr<T> result = new Constant<T>();
      for (int i = 0; i < Values.Length; i++) {
        result = Expr<T>.ConditionOp.Make(Index.Bl() == i, Values[i], result);
      }
      return result.Eval(txt);
    }
    public override string ToString() {
      return Values.ToList().ToString() + "[" + Index + "]";
    }
    public override int GetHashCode() {
      var hc = Index.GetHashCode();
      foreach (var e in Values) hc += e.GetHashCode();
      return hc;
    }
    public override bool Equals(object obj) {
      if (obj is TableExpr<T>) {
        var v = (TableExpr<T>)obj;
        if (!Index.Equals(v.Index)) return false;
        if (Values.Length != v.Values.Length) return false;
        for (int i = 0; i < Values.Length; i++) if (!Values[i].Equals(v.Values[i])) return false;
        return true;
      } else return base.Equals(obj);
    }
  }
  /// <summary>
  /// A non-branded Bl value that is obtained directly from a zero argument function.
  /// </summary>
  /// <typeparam name="T">Type of the Bl value.</typeparam>
  public class NowBlValue<T> : Expr<T> {
    /// <summary>
    /// Function used to produce Bl value.
    /// </summary>
    public Func<T> F;
    /// <summary>
    /// Optional name.
    /// </summary>
    public string Name;
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is ForceEval) {
        var txt0 = (ForceEval)txt;
        return (Eval<EVAL>.Info<T>) txt0.Force(F);
      }
      throw new NotImplementedException();
    }
    public override string ToString() {
      return Name != null ? Name : base.ToString();
    }
  }
  /// <summary>
  /// A expression that is obtained directly from a one argument function.
  /// </summary>
  /// <typeparam name="S">Type of the function's argument.</typeparam>
  /// <typeparam name="T">Type of the Bl value.</typeparam>
  public class FuncExpr<S,T> : Expr<T> {
    /// <summary>
    /// Function used to produce Bl value.
    /// </summary>
    public Func<S,T> F;
    /// <summary>
    /// The Bl value that evaluates to this Bl value's function argument.
    /// </summary>
    public Expr<S> Index;
    /// <summary>
    /// Optional name.
    /// </summary>
    public string Name;
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is ForceEval) {
        var txt0 = (ForceEval)txt;
        return (Eval<EVAL>.Info<T>) txt0.Force<S, T>(Index, F);
      } 
      throw new NotImplementedException();
    }
    public override string ToString() {
      return (Name != null ? Name : base.ToString()) + "[" + Index + "]";
    }
  }
  partial class NowExpr<T> : Expr<T>, BindableExpr<T> {
    public readonly Expr<T> Underlying;
    public NowExpr(Expr<T> Underlying) {
      this.Underlying = Underlying;
    }
    public override string ToString() {
      return Underlying + "-now";
    }
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      var v = Underlying.CurrentValue;
      return (new Constant<T>(v)).Eval(txt);
    }
    public bool BindWith(Expr<T> value) {
      return Underlying.Solve(new ApplyNowAssign(), value);
    }
  }

}
